package helmdriver

import (
	"context"
	"errors"
	"fmt"
	corev1 "gluenet/internal/agent/api/core/v1"
	"gluenet/internal/agent/driver/helmdriver/search"
	"io/ioutil"

	helmClient "github.com/mittwald/go-helm-client"
	"helm.sh/helm/v3/pkg/release"
	"helm.sh/helm/v3/pkg/repo"
)

const (
	DEFAULT_NAMESPACE = `default`

	CACHE_PATH  = `/tmp/.helmcache`
	CONFIG_PATH = `/tmp/.helmrepo`

	DEFAULT_REPO_NAME = `gluerepo`
	DEFAULT_REPO_URL  = `http://39.101.140.145:30028`
)

type MittwaldHelmDriver struct {
	ctx         context.Context
	kubeContext string
	kubeConfig  []byte
	RepoName    string
	RepoUrl     string
}

func NewDriver(ctx context.Context,
	repoName,
	repoUrl,
	kubePath string) (*MittwaldHelmDriver, error) {

	if ctx == nil {
		return nil, errors.New(`empty context`)
	}

	if repoName == "" {
		repoName = DEFAULT_REPO_NAME
	}

	if repoUrl == "" {
		repoUrl = DEFAULT_REPO_URL
	}

	d := &MittwaldHelmDriver{
		ctx:         ctx,
		kubeContext: "",
		RepoName:    repoName,
		RepoUrl:     repoUrl,
	}

	if kubePath == `` {
		return d, nil
	}

	bt, err := ioutil.ReadFile(kubePath)
	if err != nil {
		return nil, err
	}

	d.kubeContext = `minikube`
	d.kubeConfig = bt

	return d, nil

}

func (hd *MittwaldHelmDriver) newCli(namespace string) (helmClient.Client, error) {
	if namespace == "" {
		namespace = `default`
	}

	if hd.kubeConfig != nil {
		return hd.newCliWithKubeConfig(namespace)
	}

	cli, err := helmClient.New(&helmClient.Options{
		Namespace:        namespace,
		RepositoryCache:  CACHE_PATH,
		RepositoryConfig: CONFIG_PATH,
		Debug:            true,
		Linting:          true,
		DebugLog:         func(format string, v ...interface{}) {},
	})

	helmClient.NewClientFromKubeConf(&helmClient.KubeConfClientOptions{})

	if err != nil {
		return nil, err
	}

	if err = cli.AddOrUpdateChartRepo(repo.Entry{
		Name: hd.RepoName,
		URL:  hd.RepoUrl,
	}); err != nil {
		fmt.Println("add or update chart repo failed: ", err.Error())
	}

	return cli, nil
}

func (hd *MittwaldHelmDriver) newCliWithKubeConfig(namespace string) (helmClient.Client, error) {
	if namespace == "" {
		namespace = `default`
	}

	cli, err := helmClient.NewClientFromKubeConf(&helmClient.KubeConfClientOptions{
		Options: &helmClient.Options{
			Namespace:        namespace,
			RepositoryCache:  CACHE_PATH,
			RepositoryConfig: CONFIG_PATH,
			Debug:            true,
			Linting:          true,
			DebugLog:         func(format string, v ...interface{}) {},
		},
		KubeContext: hd.kubeContext,
		KubeConfig:  hd.kubeConfig,
	})

	if err != nil {
		return nil, err
	}
	if err = cli.AddOrUpdateChartRepo(repo.Entry{
		Name: hd.RepoName,
		URL:  hd.RepoUrl,
	}); err != nil {
		fmt.Println("add or update chart repo failed: ", err.Error())
	}

	return cli, nil
}

// Install Chart
func (hd *MittwaldHelmDriver) Install(namespace string, release corev1.Release) (*release.Release, error) {
	cli, err := hd.newCli(namespace)

	if err != nil {
		return nil, err
	}

	chartSpec := helmClient.ChartSpec{
		ReleaseName: release.ReleaseName,
		ChartName:   release.ChartName,
		Namespace:   namespace,
		ValuesYaml:  release.AddtionValues,
		Version:     release.Version,
		UpgradeCRDs: false,
		Wait:        false,
	}

	return cli.InstallChart(hd.ctx, &chartSpec, &helmClient.GenericHelmOptions{})
}

// Uninstall Chart
func (hd *MittwaldHelmDriver) Uninstall(namespace string, release corev1.Release) error {
	cli, err := hd.newCli(namespace)

	if err != nil {
		return err
	}

	chartSpec := helmClient.ChartSpec{
		ReleaseName: release.ReleaseName,
		ChartName:   release.ChartName,
		Namespace:   namespace,
		ValuesYaml:  release.AddtionValues,
		UpgradeCRDs: false,
		Wait:        false,
	}

	return cli.UninstallRelease(&chartSpec)
}

// Update Chart
func (hd *MittwaldHelmDriver) Update(namespace string, release corev1.Release) error {
	cli, err := hd.newCli(namespace)

	if err != nil {
		return err
	}

	chartSpec := helmClient.ChartSpec{
		ReleaseName: release.ReleaseName,
		ChartName:   release.ChartName,
		Namespace:   namespace,
		UpgradeCRDs: false,
		Wait:        false,
	}
	_, err = cli.UpgradeChart(hd.ctx, &chartSpec, &helmClient.GenericHelmOptions{})

	return err
}

// List chart
func (hd *MittwaldHelmDriver) ListCharts(name string) ([]*search.Result, error) {
	idx, err := search.BuildIndex(CONFIG_PATH, CACHE_PATH)
	if err != nil {
		return nil, err
	}

	var res []*search.Result

	if len(name) == 0 {
		// list all
		res = idx.All()
	} else {
		// get some
		res, err = idx.Search(name, 25, false)
		if err != nil {
			return nil, err
		}
	}

	search.SortScore(res)

	return res, nil
}

// Search release in given namespace
func (hd *MittwaldHelmDriver) GetRelease(namespace, releaseName string) (*release.Release, error) {
	cli, err := hd.newCli(namespace)

	if err != nil {
		return nil, err
	}

	return cli.GetRelease(releaseName)
}

// List release in given namespace
func (hd *MittwaldHelmDriver) ListRelease(namespace string) ([]*release.Release, error) {
	cli, err := hd.newCli(namespace)

	if err != nil {
		return nil, err
	}

	return cli.ListDeployedReleases()
}

// List all release in all namespaces
func (hd *MittwaldHelmDriver) ListAllRelease(namespaces []string) ([]*release.Release, error) {
	var releaselist []*release.Release
	for _, namespace := range namespaces {
		rls, err := hd.ListRelease(namespace)
		if err != nil {
			return nil, err
		}
		releaselist = append(releaselist, rls...)
	}

	return releaselist, nil
}
